﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MacomberMapSystem.Common.Serialization;
using System.Drawing.Imaging;
using MacomberMapSystem.Common.Integration;
using MacomberMapSystem.Common.CIM;
using MacomberMapSystem.Common.Database;
using System.ComponentModel.Design;
using MacomberMapSystem.Common.Internals;

namespace MacomberMapSystem.Common.User_Interfaces.Mapping
{
    /// <summary>
    /// This class provides a GDI+ network map
    /// </summary>
    public partial class ctlNetworkMap : UserControl
    {
        #region Variable declarations
        /// <summary>Our collection of tiles</summary>
        private Dictionary<Point, MM_MapTile> Tiles = new Dictionary<Point, MM_MapTile>();

        /// <summary>The point at which the mouse was pressed</summary>
        private Point MouseDownPoint;

        /// <summary>The model associated with the network map</summary>
        private MM_Database_Model Model;

        /// <summary>Our Image Attributes for drawing the map</summary>
        private ImageAttributes DrawAttributes = new ImageAttributes();

        /// <summary>Our list of substations currently visible on the screen</summary>
        public List<MM_NetworkMap_Substation> VisibleSubs = new List<MM_NetworkMap_Substation>();

        /// <summary>Our list of lines currently visible on the screen</summary>
        public List<MM_NetworkMap_Line> VisibleLines = new List<MM_NetworkMap_Line>();

        /// <summary>Our list of Boundarys currently visible on the screen</summary>
        public List<MM_NetworkMap_Boundary> VisibleBoundaries = new List<MM_NetworkMap_Boundary>();

        
        /// <summary>The menu for right-click events</summary>
        private ContextMenuStrip RightClickMenu;

        /// <summary>Our substation that may be moving</summary>
        private MM_NetworkMap_Substation MovingSubstation = null;

        /// <summary>Our substation that may be moving's latitude and longitude</summary>
        private PointD MovingSubstationOriginalLatLng;

        /// <summary>Our transmission line that is being moved</summary>
        private MM_NetworkMap_Line MovingLine = null;

        /// <summary>Our current line point that is being moved</summary>
        private PointD MovingLinePoint = PointD.Empty;

        /// <summary>The position of our moving point</summary>
        private int MovingLineIndex = 0;

        /// <summary>Our repository</summary>
        public MM_Repository Repository;

        /// <summary>The last mouse position for hover testing</summary>
        private Point LastPoint;

        /// <summary>Our internal representation of substations</summary>
        public Dictionary<MM_Substation, MM_NetworkMap_Substation> Substations=new Dictionary<MM_Substation,MM_NetworkMap_Substation>();

        /// <summary>Our internal representation of counties</summary>
        public  Dictionary<MM_Boundary, MM_NetworkMap_Boundary> Counties;

        /// <summary>Our internal representation of the state boundary</summary>
        private MM_NetworkMap_Boundary StateBoundary;

        /// <summary>Our internal representation of lines</summary>
        public Dictionary<MM_Line, MM_NetworkMap_Line> Lines = new Dictionary<MM_Line,MM_NetworkMap_Line>();

        /// <summary>
        /// The display options driving the current UI
        /// </summary>
        public MM_NetworkMap_Display DisplayOptions;

        /// <summary>
        /// The default display options for network maps
        /// </summary>
        public static MM_NetworkMap_Display DefaultDisplayOptions;

        /// <summary>The deletate for passing lat/long updates</summary>
        /// <param name="NewLatLng"></param>
        public delegate void delegateMouseLatLngChanged(PointD NewLatLng);

        /// <summary>The event indicating a lat/long change</summary>
        public event delegateMouseLatLngChanged MouseLatLngChanged;

        /// <summary>A timer for detecting mouse hovers</summary>
        private System.Timers.Timer HoverTimer = new System.Timers.Timer(1000);
        #endregion

        #region Event declarations

        /// <summary>
        /// A delegate indicating an event on an element
        /// </summary>
        /// <param name="Element">The element upon which the event occurred</param>
        /// <param name="PropertyName">The property name of our element</param>
        /// <param name="NewValue">The new value for our item</param>
        public delegate void ElementEventDelegate(MM_Element Element, String PropertyName, Object NewValue);

        /// <summary>An element was highlighted</summary>
        public event MouseEventHandler ElementMouseDown;

        /// <summary>An element has been moved</summary>
        public event MouseEventHandler ElementMouseMove;
        
        /// <summary>An element's property was updated</summary>
        public event ElementEventDelegate PropertyUpdated;
        
        #endregion
        

        #region Initialization
        /// <summary>
        /// Initialize our network map, and update our coordinates
        /// </summary>
        public ctlNetworkMap()
       {    InitializeComponent();
            ColorMatrix OutMatrix = new ColorMatrix(new float[][] { new float[] { 1, 0, 0, 0, 0 }, new float[] { 0, 1, 0, 0, 0 }, new float[] { 0, 0, 1, 0, 0 }, new float[] { 0, 0, 0, 0.5f, 0 }, new float[] { 0, 0, 0, 0, 1 } });
            this.DrawAttributes.SetColorMatrix(OutMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
            this.DoubleBuffered = true;            
            RightClickMenu = new ContextMenuStrip();
            RightClickMenu.ItemClicked += new ToolStripItemClickedEventHandler(RightClickMenu_ItemClicked);
            HoverTimer.Interval = 500;
            HoverTimer.Elapsed += new System.Timers.ElapsedEventHandler(HoverTimer_Elapsed);
        }

        
        

        /// <summary>
        /// Initailize our repository
        /// </summary>
        /// <param name="Repository"></param>
        public void InitializeRepository(MM_Repository Repository)
        {
            this.Repository = Repository; 
            DisplayOptions = new MM_NetworkMap_Display(-99.25309f, 30.76527f, 9, Repository);
            DisplayOptions.MapTileStyleChanged += new EventHandler(DisplayOptions_MapTileStyleChanged);
            DisplayOptions.MapTransparencyChanged += new EventHandler(DisplayOptions_MapTransparencyChanged);
            Counties = new Dictionary<MM_Boundary, MM_NetworkMap_Boundary>(Repository.Db.Boundaries.Count);
            foreach (MM_Boundary Bound in Repository.Db.Boundaries.Values)
            {
                MM_NetworkMap_Boundary NMBound = new MM_NetworkMap_Boundary(Bound, DisplayOptions);
                Counties.Add(Bound, NMBound);
                NMBound.ComputeCounty(DisplayOptions);
            }
            StateBoundary = new MM_NetworkMap_Boundary(Repository.Db.StateBoundary, DisplayOptions);
            StateBoundary.ComputeCounty(DisplayOptions);
        }

        /// <summary>
        /// Link up our elements with our model
        /// </summary>
        /// <param name="Model"></param>
        public List<MM_Element> InitializeModel(MM_Database_Model Model)
        {
            List<MM_Element> NewElements = new List<MM_Element>();
            
            //First, match our substations up
            MM_Substation FoundSub;
            MM_NetworkMap_Substation FoundSub2;
            foreach (CIM_Element Elem in Model.Substations.Values)                
                if (Repository.Db.Substations.TryGetValue(Elem.rdfID, out FoundSub) && Substations.TryGetValue(FoundSub, out FoundSub2))
                {
                    FoundSub2.BaseElement = Elem;
                    //FoundSub.BusNumbers = Elem.BusNumbers;
                    //TODO: Check out bus numbers
                }
                else
                {
                    MM_Substation NewSub = new MM_Substation(Elem, Repository);
                    NewElements.Add(NewSub);
                    AddOrUpdate(NewSub);
                }

            //Now, match our lines up
            MM_Line FoundLine;
            MM_NetworkMap_Line FoundLine2;
            foreach (CIM_Element Elem in Model.ElementsOfType("cim:ACLineSegment"))
                if (Repository.Db.Lines.TryGetValue(Elem.rdfID, out FoundLine) && Lines.TryGetValue(FoundLine, out FoundLine2))
                    FoundLine2.BaseElement = Elem;
                else
                {
                    MM_Line NewLine = new MM_Line(Elem, Repository);                    
                    NewElements.Add(NewLine);
                    AddOrUpdate(NewLine);
                }
            return NewElements;
        }

        /// <summary>
        /// When our map transparency options change, update our color matrix
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplayOptions_MapTransparencyChanged(object sender, EventArgs e)
        {
            float MapTransparency = DisplayOptions.MapTransparency;
            ColorMatrix OutMatrix = new ColorMatrix(new float[][] { new float[] { 1, 0, 0, 0, 0 }, new float[] { 0, 1, 0, 0, 0 }, new float[] { 0, 0, 1, 0, 0 }, new float[] { 0, 0, 0, MapTransparency, 0 }, new float[] { 0, 0, 0, 0, 1 } });
            this.DrawAttributes.SetColorMatrix(OutMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
            this.Invalidate();
        }

        /// <summary>
        /// When our map tile style changes, clear our tile collection
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplayOptions_MapTileStyleChanged(object sender, EventArgs e)
        {
            Tiles.Clear();
            this.Invalidate();
        }

        /// <summary>
        /// Set up our network map representations of substations, lines, and boundaries
        /// </summary>
        /// <param name="Model">Our CIM model</param>
        /// <param name="MapObjects">The objects to add</param>
        public void InitializeElements(MM_Database_Model Model, IEnumerable<Object> MapObjects)
        {
            this.Model = Model;
            
            //Add in all of our elements
            foreach (Object obj in MapObjects)
                if (obj is MM_Element)
                    AddOrUpdate(obj as MM_Element);                    

/*
            //Go through and find all unmatched substations, and guess at their positions
            int NotPlaced = 0, LastPlaced = -1;
            while (NotPlaced != LastPlaced)
            {
                LastPlaced = NotPlaced;
                NotPlaced = 0;
                foreach (MM_Substation Sub in Substations.Keys)
                    if (Sub.InDatabase && !Sub.Repositioned)
                    {
                        PointD LatLng = PointD.Empty;
                        int CountPoints = 0;
                        foreach (MM_Line Line in Lines.Keys)
                            if (Line.FromSubstation == Sub && (Line.ToSubstation.InDatabase || Line.ToSubstation.Repositioned))
                            {
                                CountPoints++;
                                LatLng.X += Line.ToSubstation.Longitude;
                                LatLng.Y += Line.ToSubstation.Latitude;                                
                            }
                            else if (Line.ToSubstation == Sub && (Line.FromSubstation.InDatabase || Line.FromSubstation.Repositioned))
                            {
                                CountPoints++;
                                LatLng.X += Line.FromSubstation.Longitude;
                                LatLng.Y += Line.FromSubstation.Latitude;
                            }
                        if (CountPoints > 1)
                        {
                            Sub.Repositioned = true;
                            Sub.Latitude = LatLng.Y / (float)CountPoints;
                            Sub.Longitude = LatLng.X / (float)CountPoints;
                        }
                        else
                            NotPlaced++;
                    }
                Console.WriteLine("Not placed: " + NotPlaced.ToString());

            }*/
            RecomputeElements();
        }
        #endregion

        #region Map drawing
        /// <summary>
        /// Update our map transparency
        /// </summary>
        /// <param name="TransparencyLevel">The intended transparency level</param>
        public void SetTransparency(string TransparencyLevel)
        {
            try
            {
                float MapTransparency = Convert.ToSingle(TransparencyLevel.Substring(0, TransparencyLevel.Length - 1)) / 100f;
                ColorMatrix OutMatrix = new ColorMatrix(new float[][] { new float[] { 1, 0, 0, 0, 0 }, new float[] { 0, 1, 0, 0, 0 }, new float[] { 0, 0, 1, 0, 0 }, new float[] { 0, 0, 0, MapTransparency, 0 }, new float[] { 0, 0, 0, 0, 1 } });
                this.DrawAttributes.SetColorMatrix(OutMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                this.Invalidate();
            }
            catch (Exception)
            { }
        }
        /// <summary>
        /// Draw all tiles within our map
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            if (DisplayOptions == null)
                return;

            try
            {
                //Go through all tiles, and draw accordingly           
                Point TopLeftTile = MM_NetworkMap_Display.XYToTile(DisplayOptions.TopLeftXY);
                Point BottomRightTile = MM_NetworkMap_Display.XYToTile(DisplayOptions.BottomRightXY);
                Point TopLeftTileShift = new Point(DisplayOptions.TopLeftXY.X % MM_NetworkMap_Display.TileSize.Width, DisplayOptions.TopLeftXY.Y % MM_NetworkMap_Display.TileSize.Height);
                e.Graphics.Clear(Color.Transparent);
                
                //First, draw our tiles if requested            
                MM_MapTile FoundTile;
                if (DisplayOptions.MapTiles != MM_MapTile.MapType.None)
                    for (Point ThisTile = new Point(TopLeftTile.X, TopLeftTile.Y); ThisTile.X <= BottomRightTile.X; ThisTile.X++, ThisTile.Y = TopLeftTile.Y)
                        for (; ThisTile.Y <= BottomRightTile.Y; ThisTile.Y++)
                        {
                            if (!Tiles.TryGetValue(ThisTile, out FoundTile))
                            {
                                FoundTile = new MM_MapTile(DisplayOptions.MapTiles, ThisTile, DisplayOptions.ZoomLevel, Repository.Db);
                                FoundTile.TileUpdated += new MM_MapTile.TileUpdatedDelegate(NewTile_TileUpdated);
                                Tiles.Add(ThisTile, FoundTile);
                            }
                            else if (FoundTile.Failed)
                                FoundTile.Queue();                                                                                             
                            else if (!FoundTile.Saving && FoundTile.Tile != null)
                                e.Graphics.DrawImage(FoundTile.Tile, new Rectangle(((ThisTile.X - TopLeftTile.X) * MM_NetworkMap_Display.TileSize.Width) - TopLeftTileShift.X, ((ThisTile.Y - TopLeftTile.Y) * MM_NetworkMap_Display.TileSize.Height) - TopLeftTileShift.Y, MM_NetworkMap_Display.TileSize.Width, MM_NetworkMap_Display.TileSize.Height), 0, 0, MM_NetworkMap_Display.TileSize.Width, MM_NetworkMap_Display.TileSize.Height, GraphicsUnit.Pixel, DrawAttributes);
                            else
                                e.Graphics.DrawImage(MM_MapTile.BlankBitmap, new Rectangle(((ThisTile.X - TopLeftTile.X) * MM_NetworkMap_Display.TileSize.Width) - TopLeftTileShift.X, ((ThisTile.Y - TopLeftTile.Y) * MM_NetworkMap_Display.TileSize.Height) - TopLeftTileShift.Y, MM_NetworkMap_Display.TileSize.Width, MM_NetworkMap_Display.TileSize.Height), 0, 0, MM_NetworkMap_Display.TileSize.Width, MM_NetworkMap_Display.TileSize.Height, GraphicsUnit.Pixel, DrawAttributes);
                        }

                //Shift our coordinates
                e.Graphics.TranslateTransform(-DisplayOptions.TopLeftXY.X, -DisplayOptions.TopLeftXY.Y);




                //Next, draw our counties if requested
                VisibleBoundaries.Clear();
                VisibleSubs.Clear();
                VisibleLines.Clear();
                if (DisplayOptions.ShowCounties && Counties != null)
                {
                    foreach (KeyValuePair<MM_Boundary, MM_NetworkMap_Boundary> Bound in Counties)
                        if (Bound.Value.Visible = DisplayOptions.IsVisible(Bound.Key))
                        {
                            e.Graphics.DrawPath(new Pen(Color.FromArgb(32, 32, 32), 1), Bound.Value.CountyPath);
                            VisibleBoundaries.Add(Bound.Value);
                        }
                    if (StateBoundary != null)
                        e.Graphics.DrawPath(Pens.White, StateBoundary.CountyPath);
                }


                //Next, draw our lines if requested)            
                if (Lines != null)
                        foreach (KeyValuePair<MM_Line, MM_NetworkMap_Line> Line in Lines)
                            if (double.IsNaN(Line.Value.BaseLine.FromSubstation.LatLong.X) || double.IsNaN(Line.Value.BaseLine.ToSubstation.LatLong.X))
                                Line.Value.Visible = false;
                            else if (Line.Value.Visible = DisplayOptions.IsVisible(Line.Key))
                            {

                                Line.Value.DrawLine(e.Graphics, Line.Key, DisplayOptions);
                                VisibleLines.Add(Line.Value);                                
                                }                
                   //Next, draw our substations if requested            
                if (Substations != null)
                    foreach (KeyValuePair<MM_Substation, MM_NetworkMap_Substation> Sub in Substations)
                        if (Sub.Value.Visible = DisplayOptions.IsVisible(Sub.Key))
                        {
                            Sub.Value.DrawSubstation(e.Graphics, DisplayOptions);
                            VisibleSubs.Add(Sub.Value);
                        }


            }
            catch (Exception ex)
            {
                Console.WriteLine("Error redrawing: " + ex.Message);
            }
        }





        /// <summary>
        /// Update a tile
        /// </summary>
        /// <param name="UpdatedTile"></param>
        private void NewTile_TileUpdated(MM_MapTile UpdatedTile)
        {
            this.Invalidate();
        }

        /// <summary>
        /// Add or update our element's location
        /// </summary>
        /// <param name="InElement"></param>
        public void AddOrUpdate(MM_Element InElement)
        {
            if (InElement is MM_Substation)
            {
                MM_NetworkMap_Substation Sub;
                if (Substations.TryGetValue(InElement as MM_Substation, out Sub))
                    Sub.RecomputeSubstation(DisplayOptions);
                else if (Model == null)
                    Substations.Add(InElement as MM_Substation, new MM_NetworkMap_Substation(InElement as MM_Substation, null, DisplayOptions));
                else
                    Substations.Add(InElement as MM_Substation, new MM_NetworkMap_Substation(InElement as MM_Substation, Model.FromRdfID(InElement.rdfID), DisplayOptions));
            }
            else if (InElement is MM_Line)
            {
                MM_NetworkMap_Line Line;
                if (Lines.TryGetValue(InElement as MM_Line, out Line))
                    Line.RecomputeLine(DisplayOptions);
                else if (Model == null)
                    Lines.Add(InElement as MM_Line, new MM_NetworkMap_Line(InElement as MM_Line, null, DisplayOptions));
                else
                    Lines.Add(InElement as MM_Line, new MM_NetworkMap_Line(InElement as MM_Line, Model.FromRdfID(InElement.rdfID), DisplayOptions));
            }
        }

        /// <summary>
        /// Recompue the paths for all of our counties and state
        /// </summary>
        private void RecomputeElements()
        {
            if (Counties != null)
                foreach (MM_NetworkMap_Boundary County in Counties.Values)
                    County.ComputeCounty(DisplayOptions);
            if (StateBoundary != null)
                StateBoundary.ComputeCounty(DisplayOptions);
            if (Substations != null)
                foreach (MM_NetworkMap_Substation Sub in Substations.Values)
                    Sub.RecomputeSubstation(DisplayOptions);
            if (Lines != null)
                foreach (MM_NetworkMap_Line Line in Lines.Values)
                    Line.RecomputeLine(DisplayOptions);

        
        }
        #endregion

        #region Mouse event handling
        /// <summary>
        /// Handle mouse scrolling
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            DisplayOptions.UpdateZoom(DisplayOptions.ZoomLevel + Math.Sign(e.Delta), this);
            RecomputeElements();
            
            Tiles.Clear();
            this.Invalidate();
        }


        /// <summary>
        /// Handle mouse pressing down
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left || e.Button == MouseButtons.Right)
            {
                MouseDownPoint = Cursor.Position;                
                Point CurrentPos = new Point(e.X + DisplayOptions.TopLeftXY.X, e.Y + DisplayOptions.TopLeftXY.Y);
                PointD MouseDownLatLng = MM_NetworkMap_Display.XYToLatLng(CurrentPos, DisplayOptions.ZoomLevel);                                
                foreach (MM_NetworkMap_Substation Sub in VisibleSubs)
                    if (Sub.HitTest(CurrentPos))
                    {
                        //if (e.Button == MouseButtons.Right && 
                        if (ModifierKeys == Keys.Shift)
                        {
                            MovingSubstation = Sub;
                            MovingSubstationOriginalLatLng = Sub.BaseStation.LatLong;
                        }
                        if (ElementMouseDown != null)
                            ElementMouseDown(Sub.BaseStation, e);
                        return;
                    }

                PointD FoundPoint;
                foreach (MM_NetworkMap_Line Line in VisibleLines)
                    if (!(FoundPoint = Line.HitTestPoke(CurrentPos)).IsEmpty)
                    {
                        if (ElementMouseDown != null)
                            ElementMouseDown(Line.BaseLine, e);
                        if (ModifierKeys == Keys.Shift)
                        {
                            MovingLine = Line;
                            MovingLinePoint = FoundPoint;
                            for (int a = 0; a < Line.BaseLine.Coordinates.Length; a++)
                                if (Line.BaseLine.Coordinates[a] == FoundPoint)
                                {
                                    MovingLineIndex = a;
                                    break;
                                }
                            break;
                        }
                    }


                /*
                foreach (MM_NetworkMap_Boundary Bound in VisibleBoundaries)
                    if (Bound.BaseBoundary.HitTest(MouseDownLatLng))
                    {
                        if (ElementMouseDown != null)
                            ElementMouseDown(Bound.BaseBoundary, e);
                        return;
                    }    */   
                         

            }
            base.OnMouseDown(e);
        }

        //Handle mouse movement
        /// <summary>
        /// Handle mouse movement within the network map
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (MouseLatLngChanged != null)
            {
                Point CurrentPos = new Point(e.X + DisplayOptions.TopLeftXY.X, e.Y + DisplayOptions.TopLeftXY.Y);
                MouseLatLngChanged(MM_NetworkMap_Display.XYToLatLng(CurrentPos, DisplayOptions.ZoomLevel));
            }

            if (MouseDownPoint.X > -1)
            {
                if (MovingSubstation != null && ElementMouseMove != null) // && e.Button == MouseButtons.Right)
                    ElementMouseMove(MovingSubstation, e);
                else if (MovingLine != null && ElementMouseMove != null)
                    ElementMouseMove(new KeyValuePair<MM_NetworkMap_Line,int >(MovingLine, MovingLineIndex), e);
                else if (e.Button == MouseButtons.Left && ModifierKeys != Keys.Shift)
                    DisplayOptions.UpdateFromXY(new Point(DisplayOptions.TopLeftXY.X - Cursor.Position.X + MouseDownPoint.X, DisplayOptions.TopLeftXY.Y - Cursor.Position.Y + MouseDownPoint.Y), this);
                MouseDownPoint = Cursor.Position;
                this.Refresh();
            }
            else if (LastPoint != e.Location)
            {
                LastPoint = e.Location;
                tTip.Hide(this);
                HoverTimer.Interval = 2000;
                HoverTimer.Stop();
                HoverTimer.Start();
            }

            //TODO: Handle mouse movement again
            //(this.FindForm() as frmNetworkMapEditor).UpdateInformation(e.Location, Coordinates);
            base.OnMouseMove(e);
        }


        /// <summary>
        /// Handle our hover timer
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HoverTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (InvokeRequired)
            {
                HoverTimer.Stop();
                Invoke(new System.Timers.ElapsedEventHandler(HoverTimer_Elapsed), sender, e);
            }
            else
            {
                tTip.IsBalloon = true;
                tTip.ShowAlways = false;

                Point CurPos = PointToClient(Cursor.Position);
                Point CurrentPos = new Point(CurPos.X + DisplayOptions.TopLeftXY.X, CurPos.Y + DisplayOptions.TopLeftXY.Y);

                Form OwnerForm = this.ParentForm;
                Point ParentPos = OwnerForm.PointToClient(Cursor.Position);

                foreach (MM_NetworkMap_Substation Sub in Substations.Values)
                    if (Sub.HitTest(CurrentPos))
                    {
                        tTip.Show(Sub.BaseStation.WriteToolTip(tTip), OwnerForm, ParentPos);
                        return;
                    }
                foreach (MM_NetworkMap_Line Line in Lines.Values)
                    if (Line.HitTest(CurrentPos))
                    {
                        tTip.Show(Line.BaseLine.WriteToolTip(tTip), OwnerForm, ParentPos);
                        return;
                    }
            }
        }

        /// <summary>
        /// Move a substation's lat/long, and update the coordinates to match
        /// </summary>
        /// <param name="Sub"></param>
        /// <param name="LatLong"></param>
        public void MoveSubstation(MM_Substation Sub, PointD LatLong)
        {
            Sub.LatLong = LatLong;
          
            if (PropertyUpdated != null)
                PropertyUpdated(Sub, "LatLong", LatLong);
                
            
            MovingSubstation = null;            
            Sub.FindBoundary(Repository);
            Sub.ValidatedBy = Repository.CurrentUser;
            Sub.ValidatedOn = DateTime.Now;
            Sub.UpdateRdb(Repository);
            AddOrUpdate(Sub);
        }

        /// <summary>
        /// Handle the mouse button up
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseUp(MouseEventArgs e)
        {            
            MouseDownPoint = new Point(-1, -1);
            if (e.Button == MouseButtons.Right )//&& ModifierKeys == Keys.Shift)
                HandlePopup(new Point(e.X + DisplayOptions.TopLeftXY.X, e.Y + DisplayOptions.TopLeftXY.Y));
            else if (MovingSubstation != null)
                MoveSubstation(MovingSubstation.BaseStation, MovingSubstation.BaseStation.LatLong);            
            else if (MovingLine!= null)
            {
                if (PropertyUpdated != null)
                    PropertyUpdated(MovingLine.BaseLine, "Coordinates", MovingLine.BaseLine.Coordinates);
               // MovingLine.BaseLine.UpdateRdb(Repository);
                MovingLine = null;
            }
            base.OnMouseUp(e);
        }

        /// <summary>
        /// Handle a mouse right-click up event
        /// </summary>
        /// <param name="point"></param>
        private void HandlePopup(Point point)
        {

            tTip.Hide(this);
            RightClickMenu.Items.Clear();
            RightClickMenu.Tag = point;
            foreach (MM_NetworkMap_Substation Sub in VisibleSubs)
                if (Sub.HitTest(point))
                {
                    Sub.BaseStation.WriteMenuElement(RightClickMenu);
                    if (Sub.BaseElement != null)
                        Sub.BaseElement.WriteMenuElement(RightClickMenu.Items, RightClickMenu);                        
                    RightClickMenu.Show(Cursor.Position);
                    return;
                }
            foreach (MM_NetworkMap_Line Line in VisibleLines)
                if (Line.HitTest(point))
                {
                    Line.BaseLine.WriteMenuElements(RightClickMenu);
                    if (Line.BaseElement != null)
                        Line.BaseElement.WriteMenuElement(RightClickMenu.Items, RightClickMenu);
                    RightClickMenu.Items.Add("  Total points: " + Line.BaseLine.Coordinates.Length.ToString("#,##0"));
                    ToolStripMenuItem tsi = null;
                    foreach (PointD pt in Line.BaseLine.Coordinates)
                    {
                        Point ConvPoint = MM_NetworkMap_Display.LatLngToXY(pt, DisplayOptions.ZoomLevel);
                        if (Math.Abs(ConvPoint.X - point.X) <= 3 && Math.Abs(ConvPoint.Y - point.Y) <= 3)
                        {
                            if (tsi == null)
                            {
                                tsi = RightClickMenu.Items.Add("  Coordinates") as ToolStripMenuItem;
                                tsi.DropDownItemClicked += new ToolStripItemClickedEventHandler(tsi_DropDownItemClicked);
                                }
                            tsi.DropDownItems.Add(pt.ToString());
                        }
                    }
                    if (tsi != null)
                        tsi.Text = "  Coordinates (" + tsi.DropDownItems.Count.ToString("#,##0") + ")";
                    else
                    {
                        tsi = RightClickMenu.Items.Add("  Add point") as ToolStripMenuItem;
                        tsi.Click += new EventHandler(ctlNetworkMap_Click);
                        tsi.Tag = new KeyValuePair<MM_NetworkMap_Line,Point>(Line,point);
                    }
                    RightClickMenu.Show(Cursor.Position);
                    return;
                }
            RightClickMenu.Items.Add("Add &substation");
            RightClickMenu.Items.Add("Add &line");
            RightClickMenu.Items.Add("-");
            PointD MouseDownLatLng = MM_NetworkMap_Display.XYToLatLng(point, DisplayOptions.ZoomLevel);
            Clipboard.SetText(MouseDownLatLng.X + "," + MouseDownLatLng.Y);
            RightClickMenu.Items.Add(MouseDownLatLng.X.ToString() + "," + MouseDownLatLng.Y.ToString()).Tag = MouseDownLatLng;
            RightClickMenu.Show(Cursor.Position);
        }

        /// <summary>
        /// Handle an item click in the context menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void RightClickMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            if (e.ClickedItem.Tag is PointD)
                Clipboard.SetText(((PointD)e.ClickedItem.Tag).X.ToString() + "," + ((PointD)e.ClickedItem.Tag).Y.ToString());
        }


        /// <summary>
        /// Handle the addition of a new point on our particular line
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctlNetworkMap_Click(object sender, EventArgs e)
        {
            KeyValuePair<MM_NetworkMap_Line,Point> kvp = (KeyValuePair<MM_NetworkMap_Line,Point>)(sender as ToolStripMenuItem).Tag;
            PointD LatLng = MM_NetworkMap_Display.XYToLatLng(kvp.Value, DisplayOptions.ZoomLevel);
            RectangleF TestRect = new RectangleF(kvp.Value.X - 2f, kvp.Value.Y - 2f, 4f,4f);
            
            
            //Go through our new set of points, and find out where to put them in
            List<PointD> Coordinates = new List<PointD>(kvp.Key.BaseLine.Coordinates);
            PointF LastPoint = MM_NetworkMap_Display.LatLngToXY(kvp.Key.BaseLine.Coordinates[0], DisplayOptions.ZoomLevel);
            PointF Intersection;
            for (int a = 1; a < kvp.Key.BaseLine.Coordinates.Length; a++)
            {
                PointF ThisPoint = MM_NetworkMap_Display.LatLngToXY(kvp.Key.BaseLine.Coordinates[a], DisplayOptions.ZoomLevel);
                if (MM_HitTesting.CheckIntersection(LastPoint, ThisPoint, TestRect, out Intersection))
                {
                    Coordinates.Insert(a, LatLng);
                    kvp.Key.BaseLine.Coordinates = Coordinates.ToArray();
                    kvp.Key.RecomputeLine(DisplayOptions);
                    this.Refresh();
                    break;
                }
                LastPoint = ThisPoint;
            }
        }

        /// <summary>
        /// Handle the user double-clicking on our index point
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void tsi_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            MM_NetworkMap_Line SenderLine = null;
            if (MessageBox.Show("Are you sure you want to delete the coordinates " + e.ClickedItem.Text + " from line " + "{}" + "?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
            {
                String[] splStr = e.ClickedItem.Text.Split(',');
                List<PointD> Pts = new List<PointD>(SenderLine.BaseLine.Coordinates);
                Pts.Remove(new PointD(Double.Parse(splStr[0]), Double.Parse(splStr[1])));
                SenderLine.BaseLine.Coordinates = Pts.ToArray();

            }
        }

        

       


        /// <summary>
        /// Handle user keypresses
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            
            if (e.KeyChar == (char)Keys.Escape && MovingSubstation != null)
            {
                //TODO: Handle queue handling when we hit escape
                //(ParentForm as frmNetworkMapEditor).QueueUndoEvent(MovingSubstation.BaseStation, "LatLong", MovingSubstation.BaseStation.LatLong, MovingSubstationOriginalLatLng);                
                MovingSubstation = null;
                this.Invalidate();
            }
            else if (Char.ToLower(e.KeyChar) == 'r')
            {
                DisplayOptions.UpdateFromLatLng(new PointD(-99.25309f, 30.76527f), this);
                this.Invalidate();
            }
            else if (e.KeyChar == '-')
            {
                DisplayOptions.UpdateZoom(DisplayOptions.ZoomLevel - 1, this);
                RecomputeElements();

                Tiles.Clear();
                this.Invalidate();
            }
            else if (e.KeyChar == '+')
            {
                DisplayOptions.UpdateZoom(DisplayOptions.ZoomLevel + 1, this);
                RecomputeElements();

                Tiles.Clear();
                this.Invalidate();
            }
            base.OnKeyPress(e);
        }

        /// <summary>
        /// Update and redraw the map when our size changes
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSizeChanged(EventArgs e)
        {
            if (DisplayOptions != null)
                DisplayOptions.UpdateFromLatLng(DisplayOptions.TopLeft, this);
            base.OnSizeChanged(e);
        }
        /// <summary>
        /// Update the map source to the specified string
        /// </summary>
        /// <param name="MapSource"></param>
        public void SetMapSource(String MapSource)
        {
            try
            {
                DisplayOptions.MapTiles = (MM_MapTile.MapType)Enum.Parse(typeof(MM_MapTile.MapType), MapSource);
                Tiles.Clear();
                this.Invalidate();
            }
            catch (Exception)
            { }
        }

        /// <summary>
        /// Shift the map over at the requested zoom level so that the center is the requested lat/long.
        /// </summary>
        /// <param name="CenterLatLng"></param>
        public void SetCenter(PointD CenterLatLng)
        {
            DisplayOptions.SetCenter(CenterLatLng, this);
            this.Invalidate();
        }
        #endregion

    }
}
